package tcc.wa.service.alarm;

import java.util.Calendar;
import java.util.List;

import tcc.wa.R;
import tcc.wa.model.Alert;
import tcc.wa.model.AlertType;
import tcc.wa.model.CurrentWeatherCondition;
import tcc.wa.model.DailyWeatherForecast;
import tcc.wa.model.HourlyWeatherForecast;
import tcc.wa.model.Location;
import tcc.wa.presenter.view.item.HoursInAdvanceViewItem;
import tcc.wa.service.AlertService;
import tcc.wa.service.CurrentWeatherConditionService;
import tcc.wa.service.DailyWeatherForecastService;
import tcc.wa.service.HourlyWeatherForecastService;
import tcc.wa.service.LocationService;
import tcc.wa.service.factory.ServiceFactory;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.PowerManager;
import android.util.Log;

/**
 * BroadcastReceiver called whenever an Alarm fires.
 */
public class AlarmReceiver extends BroadcastReceiver {

	private final String TAG = "AlarmReceiver";
	
	private CurrentWeatherConditionService currentWeatherConditionService;
	
	private DailyWeatherForecastService dailyWeatherForecastService;
	
	private HourlyWeatherForecastService hourlyWeatherForecastService;
	
	private AlertService alertService;
	
	private LocationService locationService;
	
	private PowerManager.WakeLock wakeLock;
	
	/* (non-Javadoc)
	 * @see android.content.BroadcastReceiver#onReceive(android.content.Context, android.content.Intent)
	 */
	@Override
	public void onReceive(Context context, Intent intent) {
		PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
		wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
		wakeLock.acquire();
		locationService = ServiceFactory.getLocationService(context);
		currentWeatherConditionService = ServiceFactory.getCurrentWeatherConditionService(context);
		dailyWeatherForecastService = ServiceFactory.getDailyWeatherForecastService(context);
		hourlyWeatherForecastService = ServiceFactory.getHourlyWeatherForecastService(context);
		alertService = ServiceFactory.getAlertService(context);
		try {
			Log.i(TAG, "Automatic forecast updating started.");
			Location location = locationService.findLast();
			if (location != null) {
				ForecastTask task = new ForecastTask(context);
				task.execute(new Location[] { location });
			} else {
				Log.w(TAG, "Location unknown. Skipping forecast update.");
				wakeLock.release();
			}
		} catch (Exception e) {
			Log.e(TAG, "Error while trying to acquire location.\n" + e.getMessage() + "\n" + e.toString());
			wakeLock.release();
		}
	}
	
	/**
	 * Helper class that updates all forecasts asynchronously.
	 * Tries to update forecasts three times and sleeps for a minute
	 * between tries.
	 */
	private class ForecastTask extends AsyncTask<Location, Void, Void> {

		private Context context;
		
		public ForecastTask(Context context) {
			this.context = context;
		}
		
		/* (non-Javadoc)
		 * @see android.os.AsyncTask#doInBackground(Params[])
		 */
		@Override
		protected Void doInBackground(Location... locations) {
			boolean success = false;
			short numberOfTries = 0;
			try {
				Location location = locations[0];
				while (!success && numberOfTries < 3) {
					Log.i(TAG, "Updating forecasts. Try " + numberOfTries + 1 + " of 3.");
					CurrentWeatherCondition currentCondition = currentWeatherConditionService.update(location);
					List<DailyWeatherForecast> dailyForecasts = dailyWeatherForecastService.update(location);
					List<HourlyWeatherForecast> hourlyForecasts = hourlyWeatherForecastService.update(location);
					currentWeatherConditionService.save(currentCondition);
					dailyWeatherForecastService.save(dailyForecasts);
					hourlyWeatherForecastService.save(hourlyForecasts);
					Log.i(TAG, "Automatic forecast updating finished.");
					checkAlerts();
					success = true;
				}
			} catch(Exception e) {
				Log.e(TAG, "Error while automatically updating forecasts: " + e.getMessage() + "\n" + e.toString());
				numberOfTries++;
				sleep(60000);
			} finally {
				wakeLock.release();
			}
			return null;
		}
		
		/**
		 * Compares registered alerts with the current forecasts.
		 * Fires a notification for each type of alert that matches the criteria.
		 */
		private void checkAlerts() {
			List<HourlyWeatherForecast> hourlyForecasts = hourlyWeatherForecastService.findLast();
			Log.i(TAG, "Starting alert check...");
			List<Alert> highTemperatureAlerts = alertService.findByType(AlertType.HIGH_TEMPERATURE);
			List<Alert> lowTemperatureAlerts = alertService.findByType(AlertType.LOW_TEMPERATURE);
			List<Alert> rainAlerts = alertService.findByType(AlertType.RAIN);
			long currentTime = Calendar.getInstance().getTimeInMillis();
			for (Alert alert : highTemperatureAlerts)
				for (HourlyWeatherForecast forecast : hourlyForecasts) {
					long forecastTime = forecast.getDate().getTime();
					long timeDifference = forecastTime - currentTime; 
					long hoursInAdvance = alert.getHoursInAdvance() * 3600000;
					if (timeDifference <= hoursInAdvance && timeDifference >= hoursInAdvance - 3600000 && forecast.getTemperature() > alert.getValue())
						fireNotification(1, R.drawable.ic_stat_hot, "Alta Temperatura", String.format("Temp. acima de %s C em %s!", alert.getValue(), new HoursInAdvanceViewItem(alert.getHoursInAdvance())));
				}
			
			for (Alert alert : lowTemperatureAlerts)
				for (HourlyWeatherForecast forecast : hourlyForecasts) {
					long forecastTime = forecast.getDate().getTime();
					long timeDifference = forecastTime - currentTime; 
					long hoursInAdvance = alert.getHoursInAdvance() * 3600000;
					if (timeDifference <= hoursInAdvance && timeDifference >= hoursInAdvance - 3600000 && forecast.getTemperature() < alert.getValue())
						fireNotification(2, R.drawable.ic_stat_cold, "Baixa Temperatura", String.format("Temp. abaixo de %s C em %s!", alert.getValue(), new HoursInAdvanceViewItem(alert.getHoursInAdvance())));
				}
			
			for (Alert alert : rainAlerts)
				for (HourlyWeatherForecast forecast : hourlyForecasts) {
					long forecastTime = forecast.getDate().getTime();
					long timeDifference = forecastTime - currentTime; 
					long hoursInAdvance = alert.getHoursInAdvance() * 3600000;
					if (timeDifference <= hoursInAdvance && timeDifference >= hoursInAdvance - 3600000 && forecast.getPrecipitation() > alert.getValue())
						fireNotification(3, R.drawable.ic_stat_rain, "Chuva", String.format("Precip. acima de %s mm em %s!", alert.getValue(), new HoursInAdvanceViewItem(alert.getHoursInAdvance())));
				}
			Log.i(TAG, "Alert check finished.");
		}
		
		/**
		 * Fires a notification.
		 *
		 * @param id the notification id
		 * @param iconId the icon id
		 * @param title the notification title
		 * @param text the notification text
		 */
		private void fireNotification(int id, int iconId, String title, String text) {
			NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
			Notification notification = new Notification();
			notification.defaults = Notification.DEFAULT_ALL;
			notification.flags |= Notification.FLAG_AUTO_CANCEL;
			notification.when = System.currentTimeMillis();
			notification.icon = iconId;
			Intent notificationIntent = new Intent(context, this.getClass());
			PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
			notification.setLatestEventInfo(context, String.format("%s - %s", "Weather Alert", title), text, contentIntent);
			notificationManager.notify(id, notification);
		}
		
		/**
		 * Sleep.
		 *
		 * @param time the time
		 */
		private void sleep(long time) {
			try {
				Thread.sleep(time);
			} catch (InterruptedException e){
				Log.e(TAG, "Error while trying to sleep.");
			}
		}
	}
}
